Conversation
Walkthrough新增基于经验值的战利品拾取流程:添加经验识别对象与初始化方法,扩展配置与参数,调整战斗任务以在特定角色存在时异步检测经验掉落并在拾取时以检测结果为网关,同时更新若干 UI 布局与模型可见性。 Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Task as AutoFightTask
participant Assets as AutoFightAssets
participant Vision as 识别/视觉系统
participant Loot as 战利品逻辑
Task->>Task: 战斗开始前判断配置与队伍角色
alt ExpKazuhaPickup 启用且角色存在
Task->>Assets: InitializeRecognitionObject(experience)
Assets-->>Task: 返回 ExperienceRa
Task->>Vision: 启动 FindExp 异步识别循环
Vision->>Assets: 使用 ExperienceRa 模板匹配
Vision-->>Task: 更新 _isExperiencePickup (true/false)
else 未启用或角色缺失
Task->>Task: 跳过经验检测
end
Task->>Loot: 拾取阶段基于 _isExperiencePickup 决策
alt _isExperiencePickup == true
Loot->>Loot: 执行正常拾取
else
Loot->>Task: 延迟/跳过拾取
end
Estimated code review effort🎯 3 (中等) | ⏱️ ~20 分钟 需重点审查:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs (1)
37-41: 将 Avatars 设为 public setter 破坏封装,存在运行时风险外部可替换/置空 Avatars,可能导致:
- AvatarCount 失真、索引越界(如 SelectAvatar/RefreshTeamAvatarIndexRectList 等依赖长度与索引)。
- IndexRect 未赋值导致识别失败。
建议改为仅公开 getter,并保留 GetAvatars() 的只读视图:
- public Avatar[] Avatars { set; get; } = []; + public Avatar[] Avatars { get; private set; } = [];
🧹 Nitpick comments (7)
BetterGenshinImpact/GameTask/AutoFight/AutoFightParam.cs (1)
82-82: 为新开关补充注释与命名对齐(可选)建议为 ExpKazuhaPickup 增补 XML 注释,说明与万叶/琴“聚集拾取”的关系;同时确认 UI 绑定字段命名与此属性完全一致,避免歧义。
BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs (2)
47-52: ExperienceRa 单实例易被覆盖;_gameScreenSize 初始化时序有隐患(可改进)
- InitializeRecognitionObject 会被多次调用生成不同经验模板,但类字段 ExperienceRa 每次都会被覆盖,后续调试显示/复用易混淆。建议移除该字段或改为 Dictionary<int, RecognitionObject> 缓存。
- _gameScreenSize 直接取 SystemControl.GetGameScreenRect(TaskContext.Instance().GameHandle) 依赖初始化时序。更稳妥做法是使用注入的 systemInfo.GameScreenSize,保持与其余素材缩放逻辑一致。
274-298: InitializeRecognitionObject:建议使用 systemInfo 宽度、避免写全局字段并默认关闭绘制当前实现功能可用,但可小幅优化以降低耦合和开销:
- 使用 this.systemInfo.GameScreenSize.Width 判定阈值,避免依赖 _gameScreenSize。
- 不写入类字段 ExperienceRa,直接返回新对象;或按经验值缓存。
- DrawOnWindow 默认应为 false(仅调试时开启),减少遮罩层刷新。
可参考如下最小变更:
- public RecognitionObject InitializeRecognitionObject(int experience) + public RecognitionObject InitializeRecognitionObject(int experience) { - var threshold = 0.9; - - if (_gameScreenSize.Width > 2560) - { - threshold =0.6; - } - else if (_gameScreenSize.Width > 1920) - { - threshold =0.6; - } - - ExperienceRa = new RecognitionObject + var width = this.systemInfo.GameScreenSize.Width; + var threshold = width > 1920 ? 0.6 : 0.9; + + var ro = new RecognitionObject { Name = experience.ToString(), RecognitionType = RecognitionTypes.TemplateMatch, - TemplateImageMat = GameTaskManager.LoadAssetImage("AutoFight", "experience_" + experience + ".png"), + TemplateImageMat = GameTaskManager.LoadAssetImage("AutoFight", $"experience_{experience}.png", this.systemInfo), RegionOfInterest = new Rect((int)(CaptureRect.Width*0.145),(int)(CaptureRect.Height*0.5), (int)(CaptureRect.Width*0.02), (int)(CaptureRect.Height*0.22)), UseMask = true, Threshold = threshold, - DrawOnWindow = true, + DrawOnWindow = false, }.InitTemplate(); - return ExperienceRa; + return ro; }BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (1)
27-35: 重复 using 可清理(可选)BetterGenshinImpact.GameTask.AutoFight.Assets 被重复导入,建议删除重复 using,减少噪音。
BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml (2)
1428-1476: “聚集材料动作”新增列与开关布局可读性改进建议
- Toggle 与说明 TextBlock 同格叠放(同列同 RowSpan)易出现遮挡/对齐偏差。建议在每个开关所在单元格内部使用垂直 StackPanel(上 Toggle、下 8pt 标签),更稳健。
- IsEnabled 绑定一般应为 OneWay,避免 UI 状态回写 VM。当前对 KazuhaPickupEnabled 的 IsEnabled 采用 TwoWay 没必要。
建议修改如下:
- IsEnabled="{Binding PathingConfig.AutoFightConfig.KazuhaPickupEnabled, Mode=TwoWay}" + IsEnabled="{Binding PathingConfig.AutoFightConfig.KazuhaPickupEnabled}" <!-- OneWay 默认 -->同时可将每个 ToggleSwitch 与其说明 TextBlock 包装进一个 StackPanel 后再放入该列,避免同格叠放导致的可视/命中问题。
1442-1443: 说明文字统一与标点修正两处页面对“基于经验/琴二次拾取”的描述略有出入,且混用英文逗号。建议统一文案并使用中文标点,便于本地化一致性。
- Text="(基于经验:通过精英经验值判断是否聚集 / 琴二次拾取:琴首次拾取空,再次拾取)" + Text="(基于经验:通过精英经验值判断是否聚集 / 琴二次拾取:首次拾取为空,再次尝试拾取)"BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml (1)
1024-1094: “聚集材料动作”四列布局的小优化(绑定方向与布局)
- 两个说明 TextBlock 与各自 Toggle 同格叠放,容易在不同 DPI/缩放下产生遮挡。建议在每个 Toggle 所在单元格内包一层垂直 StackPanel,Toggle 在上、8pt 说明在下。
- IsEnabled 对 KazuhaPickupEnabled 应用 TwoWay 没必要,改 OneWay 更稳。
建议修改:
- IsEnabled="{Binding Config.AutoFightConfig.KazuhaPickupEnabled, Mode=TwoWay}" + IsEnabled="{Binding Config.AutoFightConfig.KazuhaPickupEnabled}"另外,建议将本页与 ScriptGroupConfigView.xaml 的说明文字保持一致,并统一中文标点:
- Text="(基于经验:检测精英经验值判断聚集 / 琴二次拾取:首次拾取空,再次拾取)" + Text="(基于经验:通过精英经验值判断是否聚集 / 琴二次拾取:首次拾取为空,再次尝试拾取)"
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
BetterGenshinImpact/GameTask/AutoFight/Assets/1920x1080/experience_57.pngis excluded by!**/*.pngBetterGenshinImpact/GameTask/AutoFight/Assets/1920x1080/experience_58.pngis excluded by!**/*.pngBetterGenshinImpact/GameTask/AutoFight/Assets/1920x1080/experience_60.pngis excluded by!**/*.png
📒 Files selected for processing (7)
BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs(2 hunks)BetterGenshinImpact/GameTask/AutoFight/AutoFightConfig.cs(1 hunks)BetterGenshinImpact/GameTask/AutoFight/AutoFightParam.cs(3 hunks)BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs(6 hunks)BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs(1 hunks)BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml(1 hunks)BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs (1)
BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs (2)
Avatar(33-994)Avatar(91-100)
BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs (3)
BetterGenshinImpact/GameTask/SystemControl.cs (1)
SystemControl(11-343)BetterGenshinImpact/GameTask/TaskContext.cs (3)
TaskContext(16-77)TaskContext(24-26)TaskContext(30-33)BetterGenshinImpact/GameTask/GameTaskManager.cs (1)
GameTaskManager(29-193)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (4)
BetterGenshinImpact/GameTask/Common/TaskControl.cs (1)
TaskControl(15-226)BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs (3)
AutoFightAssets(10-299)AutoFightAssets(54-57)AutoFightAssets(59-62)BetterGenshinImpact/GameTask/Common/NewRetry.cs (1)
NewRetry(16-200)BetterGenshinImpact/View/Drawable/DrawContent.cs (1)
DrawContent(6-124)
🔇 Additional comments (4)
BetterGenshinImpact/GameTask/AutoFight/AutoFightParam.cs (1)
53-58: ExpKazuhaPickup 从配置读取的一致性良好在构造函数与 SetDefault 中均同步读取 ExpKazuhaPickup,保持了初始化路径一致性。LGTM。
Also applies to: 141-142
BetterGenshinImpact/GameTask/AutoFight/AutoFightConfig.cs (1)
160-161: 新增配置 ExpKazuhaPickup 默认值合理公开可观察布尔量默认 false,符合“显式开启”预期。建议确认:
- 配置文件序列化/反序列化已覆盖该字段(旧配置缺少该键时应安全回退为 false)。
- 设置页的绑定与此属性一致,确保热更新生效。
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (1)
307-310: 仅在队伍包含“枫原万叶/琴”时开启经验检测 — 合理Presence 检查简单直接,避免无谓的识别开销。LGTM。
BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml (1)
1022-1022: 分隔线结构化信息层级清晰加入 Separator 且显式 BorderThickness 有助于分组清晰,赞。
| #region 基于战斗检测经验值开关万叶拾取功能同步任务 | ||
|
|
||
| if (_taskParam.ExpKazuhaPickup && findExpAvatar) FindExp(cts2.Token); | ||
|
|
||
| #endregion | ||
|
|
||
| while (!cts2.Token.IsCancellationRequested) |
There was a problem hiding this comment.
FindExp 调用未管理生命周期;绘制清理时机错误
- FindExp 立即返回 CompletedTask,真正的检测在 Task.Run 内异步进行;同时 ClearAll 在外层 finally 中被过早调用,无法保证在任务结束后才清理绘制,可能导致闪烁或残留。
- 建议让 FindExp 返回内部 Task,并将清理移入该任务的 finally;调用端可按需忽略返回值或在需要时等待其完成。
可按下方方式修改 FindExp(见对应方法处的完整 diff)。
🤖 Prompt for AI Agents
In BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs around lines 318 to
324, the call to FindExp starts a background Task internally and returns a
completed Task immediately while ClearAll is invoked from the outer finally too
early; change FindExp to return the internal Task so its lifecycle is managed by
the returned Task, move drawing cleanup (ClearAll) into the finally block inside
that internal Task, and update this call site to await or ignore the returned
Task as appropriate (e.g., if you don't need to wait, explicitly ignore the Task
to document intent).
| //基于万叶经验值判断是否拾取 | ||
| private static Task FindExp(CancellationToken cts2) | ||
| { | ||
| var autoFightAssets = AutoFightAssets.Instance; | ||
|
|
||
| try | ||
| { | ||
| Task.Run(() => | ||
| { | ||
| _isExperiencePickup = false; | ||
| var expLogo = false; | ||
|
|
||
| var experienceRas = new[] | ||
| { | ||
| autoFightAssets.InitializeRecognitionObject(60), | ||
| autoFightAssets.InitializeRecognitionObject(58), | ||
| autoFightAssets.InitializeRecognitionObject(57), | ||
| }; | ||
|
|
||
| while (!(_isExperiencePickup || !FightStatusFlag) && !cts2.IsCancellationRequested) | ||
| { | ||
| try | ||
| { | ||
| cts2.ThrowIfCancellationRequested(); | ||
|
|
||
| var result = NewRetry.WaitForAction(() => | ||
| { | ||
| using (var ra = CaptureToRectArea()) | ||
| { | ||
| _isExperiencePickup = experienceRas.Any(experienceRa => | ||
| { | ||
| var isExist = ra.Find(experienceRa); | ||
| if (!isExist.IsExist()) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| var pixelValue1 = ra.SrcMat.At<Vec3b>(isExist.Y, isExist.X - 147); //经验值图标,在2K以上时匹配度0.6,这个经验值颜色尤为重要 | ||
| expLogo = pixelValue1[0] == 253 && pixelValue1[1] == 247 && pixelValue1[2] == 172; | ||
|
|
||
| return expLogo; | ||
| }); | ||
| } | ||
| return _isExperiencePickup; | ||
| }, cts2, 1, 100).Result; | ||
| } | ||
| catch (OperationCanceledException ex) | ||
| { | ||
| Console.WriteLine($"检测经验发生异常: {ex.Message}"); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| // Console.WriteLine($"检测怪物经验发生异常: {ex.Message}"); | ||
| } | ||
|
|
||
| if (_isExperiencePickup) Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取","精英","启用" ); | ||
|
|
||
| } | ||
|
|
||
| cts2.ThrowIfCancellationRequested(); | ||
|
|
||
| }, cts2); | ||
| } | ||
| catch (OperationCanceledException ex) | ||
| { | ||
| Console.WriteLine($"检测经验发生异常: {ex.Message}"); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Console.WriteLine($"检测怪物经验发生异常: {ex.Message}"); | ||
| } | ||
| finally | ||
| { | ||
| VisionContext.Instance().DrawContent.ClearAll(); | ||
| } | ||
|
|
||
| return Task.CompletedTask; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
FindExp:将清理放入任务 finally,避免 .Result 阻塞并加入边界检查/色差容忍
- 将外层 finally 的 ClearAll 移入 Task.Run 的 finally,使清理与任务生命周期一致;
- 避免 .Result 阻塞,直接 await WaitForAction;
- 采样点加入边界保护;
- 颜色匹配加入微小容忍,降低分辨率/抗锯齿差异带来的误判。
建议变更:
- private static Task FindExp(CancellationToken cts2)
- {
- var autoFightAssets = AutoFightAssets.Instance;
- try
- {
- Task.Run(() =>
- {
- _isExperiencePickup = false;
- var expLogo = false;
-
- var experienceRas = new[]
- {
- autoFightAssets.InitializeRecognitionObject(60),
- autoFightAssets.InitializeRecognitionObject(58),
- autoFightAssets.InitializeRecognitionObject(57),
- };
-
- while (!(_isExperiencePickup || !FightStatusFlag) && !cts2.IsCancellationRequested)
- {
- try
- {
- cts2.ThrowIfCancellationRequested();
-
- var result = NewRetry.WaitForAction(() =>
- {
- using (var ra = CaptureToRectArea())
- {
- _isExperiencePickup = experienceRas.Any(experienceRa =>
- {
- var isExist = ra.Find(experienceRa);
- if (!isExist.IsExist())
- {
- return false;
- }
-
- var pixelValue1 = ra.SrcMat.At<Vec3b>(isExist.Y, isExist.X - 147); //经验值图标,在2K以上时匹配度0.6,这个经验值颜色尤为重要
- expLogo = pixelValue1[0] == 253 && pixelValue1[1] == 247 && pixelValue1[2] == 172;
-
- return expLogo;
- });
- }
- return _isExperiencePickup;
- }, cts2, 1, 100).Result;
- }
- catch (OperationCanceledException ex)
- {
- Console.WriteLine($"检测经验发生异常: {ex.Message}");
- }
- catch (Exception ex)
- {
- // Console.WriteLine($"检测怪物经验发生异常: {ex.Message}");
- }
-
- if (_isExperiencePickup) Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取","精英","启用" );
-
- }
-
- cts2.ThrowIfCancellationRequested();
-
- }, cts2);
- }
- catch (OperationCanceledException ex)
- {
- Console.WriteLine($"检测经验发生异常: {ex.Message}");
- }
- catch (Exception ex)
- {
- Console.WriteLine($"检测怪物经验发生异常: {ex.Message}");
- }
- finally
- {
- VisionContext.Instance().DrawContent.ClearAll();
- }
-
- return Task.CompletedTask;
- }
+ private static Task FindExp(CancellationToken token)
+ {
+ var assets = AutoFightAssets.Instance;
+ return Task.Run(async () =>
+ {
+ try
+ {
+ _isExperiencePickup = false;
+ var experienceRas = new[]
+ {
+ assets.InitializeRecognitionObject(60),
+ assets.InitializeRecognitionObject(58),
+ assets.InitializeRecognitionObject(57),
+ };
+
+ while (!_isExperiencePickup && FightStatusFlag && !token.IsCancellationRequested)
+ {
+ try
+ {
+ token.ThrowIfCancellationRequested();
+ var detected = await NewRetry.WaitForAction(() =>
+ {
+ using var ra = CaptureToRectArea();
+ foreach (var ro in experienceRas)
+ {
+ var match = ra.Find(ro);
+ if (!match.IsExist()) continue;
+ var sampleX = match.X - 147;
+ var sampleY = match.Y;
+ if (sampleX < 0 || sampleY < 0 || sampleX >= ra.SrcMat.Cols || sampleY >= ra.SrcMat.Rows)
+ {
+ continue;
+ }
+ var p = ra.SrcMat.At<Vec3b>(sampleY, sampleX);
+ // 颜色近似(±3)以提升鲁棒性
+ bool near = Math.Abs(p[0] - 253) <= 3 && Math.Abs(p[1] - 247) <= 3 && Math.Abs(p[2] - 172) <= 3;
+ if (near) return (_isExperiencePickup = true);
+ }
+ return _isExperiencePickup;
+ }, token, retryTimes: 1, delayMs: 100);
+
+ if (detected)
+ {
+ Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取", "精英", "启用");
+ }
+ }
+ catch (OperationCanceledException) { /* ignore */ }
+ catch { /* ignore */ }
+ }
+ }
+ finally
+ {
+ VisionContext.Instance().DrawContent.ClearAll();
+ }
+ }, token);
+ }如需进一步稳妥,可将 FightStatusFlag 也以 volatile/Interlocked 保护(参见上一条评论)。
🤖 Prompt for AI Agents
In BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs around lines 768 to
845: the review asks to move the cleanup into the spawned task’s finally, avoid
blocking .Result by awaiting WaitForAction, add bounds checks for the sample
coordinate (ensure X-147 >= 0 and Y within image height) and add a small
tolerance when comparing pixel RGB values to account for resolution/antialiasing
differences (e.g., compare absolute difference <= tolerance). Change the
synchronous Task-returning method to use async/await for the inner loop (await
NewRetry.WaitForAction(...)) and wrap the Task.Run delegate with try/finally
where VisionContext.Instance().DrawContent.ClearAll() is called, and ensure any
shared flags (like FightStatusFlag) are accessed in a thread-safe manner
(volatile or Interlocked) as needed.
| <Grid Margin="16"> | ||
| <Grid.RowDefinitions> | ||
| <RowDefinition Height="Auto" /> | ||
| <RowDefinition Height="Auto" /> | ||
| </Grid.RowDefinitions> | ||
| <Grid.ColumnDefinitions> | ||
| <ColumnDefinition Width="*" /> | ||
| <ColumnDefinition Width="Auto" /> | ||
| </Grid.ColumnDefinitions> | ||
| <ui:TextBlock Grid.Row="0" | ||
| Grid.Column="0" | ||
| FontTypography="Body" | ||
| Text="只拾取精英掉落模式" | ||
| TextWrapping="Wrap" /> | ||
| <ui:TextBlock Grid.Row="1" | ||
| Grid.Column="0" | ||
| Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}" | ||
| Text="需要路径文件支持,需勾选区分怪物拾取才生效,可通过仓库标签查看,精英怪物可以在点位的更多》扩展配置》怪物标签中,可标记。只拾取标记为精英或传奇的点位。非精英允许自动拾取:战斗过程中掉落脚下的可以自动拾取,但不会执行万叶拾取和自动拾取配置逻辑。非精英关闭拾取:战斗过程中掉落到脚下的也不会自动拾取。" | ||
| TextWrapping="Wrap" /> | ||
| <ComboBox Grid.Row="0" | ||
| Grid.RowSpan="2" | ||
| Grid.Column="1" | ||
| MinWidth="75" | ||
| SelectedValuePath="Key" | ||
| DisplayMemberPath="Value" | ||
| Margin="0,0,36,0" | ||
| ItemsSource="{Binding OnlyPickEliteDropsSource}" | ||
| SelectedValue="{Binding PathingConfig.AutoFightConfig.OnlyPickEliteDropsMode}" /> | ||
| </Grid> | ||
| <Grid Margin="16"> | ||
| <Grid.RowDefinitions> | ||
| <RowDefinition Height="Auto" /> | ||
| <RowDefinition Height="Auto" /> | ||
| </Grid.RowDefinitions> | ||
| <Grid.ColumnDefinitions> | ||
| <ColumnDefinition Width="*" /> | ||
| <ColumnDefinition Width="Auto" /> | ||
| </Grid.ColumnDefinitions> | ||
| <ui:TextBlock Grid.Row="0" | ||
| Grid.Column="0" | ||
| FontTypography="Body" | ||
| Text="拾取战斗人次阈值" | ||
| TextWrapping="Wrap" /> | ||
| <ui:TextBlock Grid.Row="1" | ||
| Grid.Column="0" | ||
| Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}" | ||
| Text="拾取战斗人次阈值,当战斗人次小于一定次数,就结束战斗情况下,不触发拾取掉落物和万叶拾取配置,只有不小于2时才生效。" | ||
| TextWrapping="Wrap" /> | ||
| <ui:TextBox Grid.Row="0" | ||
| Grid.RowSpan="2" | ||
| Grid.Column="1" | ||
| MinWidth="120" | ||
| Margin="0,0,36,0" | ||
| Text="{Binding PathingConfig.AutoFightConfig.BattleThresholdForLoot}" /> | ||
| </Grid> |
There was a problem hiding this comment.
重复的配置区块需移除,避免混淆与不一致
这里新增了“只拾取精英掉落模式”和“拾取战斗人次阈值”两个 Grid,但文件中已分别存在相同功能区块(Lines 1490-1514 与 1570-1597)。重复会造成:
- UI 冗余与用户困惑;
- 属性双向绑定到同一字段时的聚焦/验证行为不一致;
- MinWidth 不一致(75 vs 100)导致视觉不齐。
建议删除本段重复区块,保留前后已有的单一实现;若保留,请至少统一 MinWidth=100。
删除重复区块的示例补丁:
- <Grid Margin="16">
- <!-- 只拾取精英掉落模式(重复) -->
- ...
- </Grid>
-
- <Grid Margin="16">
- <!-- 拾取战斗人次阈值(重复) -->
- ...
- </Grid>Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml around lines
1515 to 1569 there is a duplicate UI configuration block for "只拾取精英掉落模式" and
"拾取战斗人次阈值" that repeats functionality already present at lines ~1490-1514 and
~1570-1597; remove this duplicate block to avoid UI redundancy and binding
conflicts, or if you must keep it, ensure it matches the original implementation
exactly (use MinWidth=100 for consistency and keep the same
ItemsSource/SelectedValue/Text bindings and SelectedValuePath/DisplayMemberPath
settings) so bindings, focus/validation and visual alignment remain consistent.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (3)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (3)
58-58: 跨线程访问 _isExperiencePickup 缺少内存可见性保证(竞态条件)该标志在 FindExp 的后台任务中写入(第 777、797、806 行),在主流程中读取(第 496、507、513 行)。当前为普通静态字段,存在内存可见性风险,线程可能读取到旧值。
建议将其声明为
volatile以确保跨线程内存可见性。- private static bool _isExperiencePickup = false; + private static volatile bool _isExperiencePickup = false;
318-322: FindExp 任务生命周期未管理,清理时机错误FindExp 立即返回 CompletedTask,真正的检测在 Task.Run 内异步进行。同时 ClearAll 在 FindExp 方法的外层 finally 中被过早调用(第 839-842 行),无法保证在任务结束后才清理绘制内容。
建议让 FindExp 返回内部 Task,并将清理移入该任务的 finally 块中。调用端可根据需要忽略或等待返回的 Task。
参见第 768-845 行 FindExp 方法的详细修改建议。
768-845: FindExp 方法存在多个未解决的关键问题该方法存在以下问题(与先前评审意见一致):
- 生命周期管理错误:返回
Task.CompletedTask而实际检测在 Task.Run 内部异步执行,导致调用方无法追踪任务状态- 阻塞调用:第 812 行使用
.Result阻塞等待,应改用await- 清理时机错误:第 839-842 行的 ClearAll 在外层 finally 中执行,会在后台任务完成前被调用
- 缺少边界检查:第 805 行
isExist.X - 147可能导致负数或越界访问- 颜色匹配过于严格:第 806 行使用精确 RGB 匹配,不同分辨率/抗锯齿下容易误判
建议的修复方案(来自先前评审):
- private static Task FindExp(CancellationToken cts2) + private static Task FindExp(CancellationToken token) { var autoFightAssets = AutoFightAssets.Instance; - try - { - Task.Run(() => + return Task.Run(async () => + { + try { _isExperiencePickup = false; - var expLogo = false; var experienceRas = new[] { autoFightAssets.InitializeRecognitionObject(60), autoFightAssets.InitializeRecognitionObject(58), autoFightAssets.InitializeRecognitionObject(57), }; - while (!(_isExperiencePickup || !FightStatusFlag) && !cts2.IsCancellationRequested) + while (!_isExperiencePickup && FightStatusFlag && !token.IsCancellationRequested) { try { - cts2.ThrowIfCancellationRequested(); - - var result = NewRetry.WaitForAction(() => + token.ThrowIfCancellationRequested(); + await NewRetry.WaitForAction(() => { using (var ra = CaptureToRectArea()) { - _isExperiencePickup = experienceRas.Any(experienceRa => + foreach (var experienceRa in experienceRas) { var isExist = ra.Find(experienceRa); if (!isExist.IsExist()) { - return false; + continue; } - - var pixelValue1 = ra.SrcMat.At<Vec3b>(isExist.Y, isExist.X - 147); - expLogo = pixelValue1[0] == 253 && pixelValue1[1] == 247 && pixelValue1[2] == 172; - - return expLogo; - }); + + var sampleX = isExist.X - 147; + var sampleY = isExist.Y; + + // 边界检查 + if (sampleX < 0 || sampleY < 0 || + sampleX >= ra.SrcMat.Cols || sampleY >= ra.SrcMat.Rows) + { + continue; + } + + var p = ra.SrcMat.At<Vec3b>(sampleY, sampleX); + // 颜色容差匹配(±3)提升鲁棒性 + bool colorMatch = Math.Abs(p[0] - 253) <= 3 && + Math.Abs(p[1] - 247) <= 3 && + Math.Abs(p[2] - 172) <= 3; + + if (colorMatch) + { + _isExperiencePickup = true; + break; + } + } } return _isExperiencePickup; - }, cts2, 1, 100).Result; + }, token, 1, 100); + + if (_isExperiencePickup) + { + Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取", "精英", "启用"); + } } - catch (OperationCanceledException ex) - { - Console.WriteLine($"检测经验发生异常: {ex.Message}"); - } - catch (Exception ex) - { - // Console.WriteLine($"检测怪物经验发生异常: {ex.Message}"); - } - - if (_isExperiencePickup) Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取","精英","启用" ); - + catch (OperationCanceledException) { /* 正常取消,忽略 */ } + catch { /* 检测异常,继续循环 */ } } - - cts2.ThrowIfCancellationRequested(); - - }, cts2); - } - catch (OperationCanceledException ex) - { - Console.WriteLine($"检测经验发生异常: {ex.Message}"); - } - catch (Exception ex) - { - Console.WriteLine($"检测怪物经验发生异常: {ex.Message}"); - } - finally - { - VisionContext.Instance().DrawContent.ClearAll(); - } - - return Task.CompletedTask; + } + finally + { + VisionContext.Instance().DrawContent.ClearAll(); + } + }, token); }
🧹 Nitpick comments (3)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (3)
26-26: 删除重复的 using 声明第 26 行和第 34 行都导入了
BetterGenshinImpact.GameTask.AutoFight.Assets,存在重复。using BetterGenshinImpact.GameTask.AutoFight.Assets; using BetterGenshinImpact.View.Drawable; using BetterGenshinImpact.Core.Recognition.OpenCv; using BetterGenshinImpact.GameTask.Common.BgiVision; using Vanara.PInvoke; using BetterGenshinImpact.GameTask.AutoPathing.Handler; using BetterGenshinImpact.GameTask.AutoPick.Assets; using BetterGenshinImpact.GameTask.AutoPathing.Model; -using BetterGenshinImpact.GameTask.AutoFight.Assets;Also applies to: 34-34
496-500: 优化延迟条件逻辑当
_isExperiencePickup为 false 时添加延迟等待经验显示的逻辑是合理的,但建议明确日志输出的含义。当前代码在所有满足条件的情况下都会延迟 1 秒,可能影响已检测到经验的情况。建议确认此延迟仅在确实需要等待经验值显示时执行。
507-510: 复杂的布尔条件可考虑添加注释第 507 行的条件逻辑正确,但较为复杂。建议添加注释说明何时跳过拾取:
- 战斗人次低于阈值,且(未启用经验值判断 或 未检测到经验值)时跳过。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs(6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (4)
BetterGenshinImpact/GameTask/Common/TaskControl.cs (1)
TaskControl(15-226)BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs (3)
AutoFightAssets(10-299)AutoFightAssets(54-57)AutoFightAssets(59-62)BetterGenshinImpact/GameTask/Common/NewRetry.cs (1)
NewRetry(16-200)BetterGenshinImpact/View/Drawable/DrawContent.cs (1)
DrawContent(6-124)
🔇 Additional comments (2)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (2)
307-309: 逻辑正确,实现清晰通过 HashSet 检查队伍中是否存在拾取功能角色(枫原万叶或琴)的逻辑正确且高效。
515-515: 拾取门控逻辑正确基于经验值检测结果的拾取门控条件实现正确,符合需求。
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (2)
318-322: FindExp 调用未管理生命周期;请改为返回内部 Task 并在调用处显式持有/忽略当前
FindExp(cts2.Token)内部再Task.Run且方法本身返回Task.CompletedTask,调用方无法管理其生命周期;外层清理也可能过早执行。建议让FindExp直接返回内部任务,并在此处显式持有(或用_ =明确忽略)。- if (_taskParam.ExpKazuhaPickup && findExpAvatar) FindExp(cts2.Token); + if (_taskParam.ExpKazuhaPickup && findExpAvatar) + { + // 显式启动并忽略返回的 Task,表明有意异步执行且不等待 + _ = FindExp(cts2.Token); + }
768-845: FindExp 存在多处并发与正确性隐患:返回值、清理时机、.Result 阻塞、像素越界与颜色容忍度
- 方法返回
CompletedTask,而真实工作在内部 Task,调用方无法管理其生命周期;外层 finally 的ClearAll()可能比内部任务先执行,导致闪烁/残留。- 使用
NewRetry.WaitForAction(...).Result在线程池线程上同步阻塞,易造成线程饥饿与不可控延迟。- 采样点
isExist.X - 147未做边界检查,可能越界访问SrcMat。- 使用严格 RGB 等值判断,易受分辨率/抗锯齿影响误判。
- 循环条件读取
FightStatusFlag为普通属性(非 volatile/Interlocked),跨线程可见性无保证。建议重构如下:
- 让
FindExp返回内部Task,并将ClearAll()放入其finally。- 用
await调用WaitForAction,避免.Result。- 对采样坐标做边界检查。
- 颜色判断加入微小容忍度(如 ±3)。
- 为
FightStatusFlag增加内存可见性保证(见下方附加 diff)。- private static Task FindExp(CancellationToken cts2) - { - var autoFightAssets = AutoFightAssets.Instance; - - try - { - Task.Run(() => - { - _isExperiencePickup = false; - var expLogo = false; - - var experienceRas = new[] - { - autoFightAssets.InitializeRecognitionObject(60), - autoFightAssets.InitializeRecognitionObject(58), - autoFightAssets.InitializeRecognitionObject(57), - }; - - while (!(_isExperiencePickup || !FightStatusFlag) && !cts2.IsCancellationRequested) - { - try - { - cts2.ThrowIfCancellationRequested(); - - var result = NewRetry.WaitForAction(() => - { - using (var ra = CaptureToRectArea()) - { - _isExperiencePickup = experienceRas.Any(experienceRa => - { - var isExist = ra.Find(experienceRa); - if (!isExist.IsExist()) - { - return false; - } - - var pixelValue1 = ra.SrcMat.At<Vec3b>(isExist.Y, isExist.X - 147); //经验值图标,在2K以上时匹配度0.6,这个经验值颜色尤为重要 - expLogo = pixelValue1[0] == 253 && pixelValue1[1] == 247 && pixelValue1[2] == 172; - - return expLogo; - }); - } - return _isExperiencePickup; - }, cts2, 1, 100).Result; - } - catch (OperationCanceledException ex) - { - Console.WriteLine($"检测经验发生异常: {ex.Message}"); - } - catch (Exception ex) - { - // Console.WriteLine($"检测怪物经验发生异常: {ex.Message}"); - } - - if (_isExperiencePickup) Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取","精英","启用" ); - - } - - cts2.ThrowIfCancellationRequested(); - - }, cts2); - } - catch (OperationCanceledException ex) - { - Console.WriteLine($"检测经验发生异常: {ex.Message}"); - } - catch (Exception ex) - { - Console.WriteLine($"检测怪物经验发生异常: {ex.Message}"); - } - finally - { - VisionContext.Instance().DrawContent.ClearAll(); - } - - return Task.CompletedTask; - } + private static Task FindExp(CancellationToken token) + { + var assets = AutoFightAssets.Instance; + return Task.Run(async () => + { + try + { + _isExperiencePickup = false; + var experienceRas = new[] + { + assets.InitializeRecognitionObject(60), + assets.InitializeRecognitionObject(58), + assets.InitializeRecognitionObject(57), + }; + + while (!_isExperiencePickup && FightStatusFlag && !token.IsCancellationRequested) + { + try + { + token.ThrowIfCancellationRequested(); + var detected = await NewRetry.WaitForAction(() => + { + using var ra = CaptureToRectArea(); + foreach (var ro in experienceRas) + { + var match = ra.Find(ro); + if (!match.IsExist()) continue; + var sx = match.X - 147; + var sy = match.Y; + if (sx < 0 || sy < 0 || sx >= ra.SrcMat.Cols || sy >= ra.SrcMat.Rows) continue; + var p = ra.SrcMat.At<Vec3b>(sy, sx); + const int tol = 3; + bool near = + Math.Abs(p[0] - 253) <= tol && + Math.Abs(p[1] - 247) <= tol && + Math.Abs(p[2] - 172) <= tol; + if (near) return (_isExperiencePickup = true); + } + return _isExperiencePickup; + }, token, retryTimes: 1, delayMs: 100); + + if (detected) + { + Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取", "精英", "启用"); + } + } + catch (OperationCanceledException) { /* ignore */ } + catch { /* ignore */ } + } + } + finally + { + VisionContext.Instance().DrawContent.ClearAll(); + } + }, token); + }附:为
FightStatusFlag增加可见性保证(在属性定义处修改)。-public static bool FightStatusFlag { get; set; } = false; +private static bool _fightStatusFlag; +public static bool FightStatusFlag +{ + get => Volatile.Read(ref _fightStatusFlag); + set => Volatile.Write(ref _fightStatusFlag, value); +}
🧹 Nitpick comments (2)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (2)
27-35: 移除重复 using 以消除冗余
using BetterGenshinImpact.GameTask.AutoFight.Assets;出现两次(行 27 与行 34)。保留一次即可。-using BetterGenshinImpact.GameTask.AutoFight.Assets;
496-501: 等待经验显示时使用局部取消令牌,避免外部取消不一致此处建议用
cts2.Token保持与本次战斗生命周期一致;并建议在日志后快速返回而非固定 1s 等待(可由WaitForAction驱动)。- await Delay(1000, ct); + await Delay(1000, cts2.Token);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs(6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (4)
BetterGenshinImpact/GameTask/Common/TaskControl.cs (1)
TaskControl(15-226)BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs (3)
AutoFightAssets(10-299)AutoFightAssets(54-57)AutoFightAssets(59-62)BetterGenshinImpact/GameTask/Common/NewRetry.cs (1)
NewRetry(16-200)BetterGenshinImpact/View/Drawable/DrawContent.cs (1)
DrawContent(6-124)
🔇 Additional comments (2)
BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs (2)
58-58: 已采用 volatile 保证跨线程可见性 — LGTM对
_isExperiencePickup使用volatile恰当,解决了读写可见性问题。
507-516: 语义确认:经验命中时绕过“战斗人次”门槛当前条件使得“经验识别命中”会放行拾取,即使
countFight < BattleThresholdForLoot。确认这是期望行为吗?若希望两者都满足,可改为 AND。
1、通过判断经验死亡的经验值判断是否进行材料聚集。
2、目前通过判断分辨率去调整匹配度,适配1080、2K和4K。
3、测试很久了,目前相当稳定。
Summary by CodeRabbit
发行说明
新功能
用户界面